using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Acms.SqlDal;
using System.Security.Cryptography;

namespace Acms.Bll
{
    public class UserSecurity : BaseItem
    {
        #region Construtors
        private UserSecurity() : base ()
        {
        }

        private UserSecurity(Hashtable table) : base (table)
        {
        }

        private UserSecurity(Hashtable table, List<UserSecurity> collection) : base (table, collection)
        {
        }
        #endregion

        #region Static Getters
        private static List<UserSecurity> LoadAll(Hashtable[] tables)
        {
            List<UserSecurity> tmp = new List<UserSecurity>();
            if (tables != null)
            {
                foreach (Hashtable table in tables)
                {
                    tmp.Add(new UserSecurity(table, tmp));
                }
            }
            return tmp;
        }

        private static UserSecurity LoadItem(Hashtable table)
        {
            if (table != null)
            {
                return new UserSecurity(table);
            }
            else
            {
                return null;
            }
        }

        protected override Base CreateDal()
        {
            return new UserSecurityDal();
        }

        public static UserSecurity CreateInstance()
        {
            return new UserSecurity();
        }
        public static UserSecurity GetById(int id)
        {
            return GetById(id, false);
        }

        public static UserSecurity GetById(int id, bool updateLastActivity)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadItem(dal.GetById(id, updateLastActivity));
        }

        public static UserSecurity GetByUserName(string username)
        {
            return GetByUserName(username, false);
        }

        public static UserSecurity GetByUserName(string username, bool updateLastActivity)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadItem(dal.GetByUserName(username, updateLastActivity));
        }

        public static List<UserSecurity> GetAll()
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadAll(dal.GetAll());
        }

        public static List<UserSecurity> GetAllPaged(string sortExpression, int startIndex, int pageSize, ref int totalRecords)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadAll(dal.GetAllPaged(sortExpression, startIndex, pageSize, ref totalRecords));
        }

        public static List<UserSecurity> GetAllByEmailAddressPaged(string emailAddress, string sortExpression, int startRecord, int pageSize, ref int totalRecords)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadAll(dal.GetAllByEmailAddressPaged(emailAddress, sortExpression, startRecord, pageSize, ref totalRecords));
        }

        public static List<UserSecurity> GetAllByUsernamePaged(string userName, string sortExpression, int startRecord, int pageSize, ref int totalRecords)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadAll(dal.GetAllByUsernamePaged(userName, sortExpression, startRecord, pageSize, ref totalRecords));
        }

        public static int CountOnline()
        {
            UserSecurityDal dal = new UserSecurityDal();
            return dal.CountOnline(20);//20 minute session
        }
        #endregion

        #region Properties
        protected override void Load(Hashtable table)
        {
            id = (int)table[UserSecurityDal.Fields.Id];
            userName = (string)table[UserSecurityDal.Fields.UserName];
            password = (string)table[UserSecurityDal.Fields.Password];
            salt = (int)table[UserSecurityDal.Fields.Salt];
            passwordQuestion = (string)table[UserSecurityDal.Fields.PasswordQuestion];
            passwordAnswer = (string)table[UserSecurityDal.Fields.PasswordAnswer];
            emailAddress = (string)table[UserSecurityDal.Fields.EmailAddress];
            created = (DateTime)table[UserSecurityDal.Fields.Created];
            authenticated = (bool)table[UserSecurityDal.Fields.Authenticated];
            locked = (bool)table[UserSecurityDal.Fields.Locked];
            lastLogin = (DateTime?)table[UserSecurityDal.Fields.LastLogin];
            lastActivity = (DateTime?)table[UserSecurityDal.Fields.LastActivity];
            lastChangedPassword = (DateTime?)table[UserSecurityDal.Fields.LastChangedPassword];
            lastLocked = (DateTime?)table[UserSecurityDal.Fields.LastLocked];
        }

        private int id;
    	public int Id
	    {
	        get { return id;}
	    }

        private string userName;
        public string UserName
        {
            get { return userName; }
            set { userName = value; }
        }

        private string password;

        private int salt;

        private string passwordQuestion;
        public string PasswordQuestion
        {
            get { return passwordQuestion; }
            set { passwordQuestion = value; }
        }

        private string passwordAnswer;
        public string PasswordAnswer
        {
            get { return passwordAnswer; }
            set { passwordAnswer = value; }
        }

        private string emailAddress;
        public string EmailAddress
        {
            get { return emailAddress; }
            set { emailAddress = value; }
        }

        private bool authenticated;
        public bool Authenticated
        {
            get { return authenticated; }
            set { authenticated = value;}
        }

        private DateTime created;
        public DateTime Created
        {
            get { return created; }
        }

        private bool locked;
        public bool Locked
        {
            get { return locked; }
        }

        private DateTime? lastLogin;
        public DateTime? LastLogin
        {
            get { return lastLogin; }
        }

        private DateTime? lastActivity;
        public DateTime? LastActivity
        {
            get { return lastActivity; }
        }

        private DateTime? lastChangedPassword;
        public DateTime? LastChangedPassword
        {
            get { return lastChangedPassword; }
        }

        private DateTime? lastLocked;
        public DateTime? LastLocked
        {
            get { return lastLocked; }
        }
        #endregion

        #region CRUD Methods
        public new void Update()
        {
            base.Update();
        }

        public new void Delete()
        {
            base.Delete();
        }

        protected override void DeleteRecord(object concurencyId, Base dal)
        {
            ((UserSecurityDal)dal).Delete(id, concurencyId);
        }
        #endregion

        #region Private Methods
        private void CreateSalt()
        {
            Byte[] _saltBytes = new Byte[4];
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            rng.GetBytes(_saltBytes);

            salt = ((((int)_saltBytes[0]) << 24) + (((int)_saltBytes[1]) << 16) + (((int)_saltBytes[2]) << 8) + ((int)_saltBytes[3]));
        }

        public bool SetPassword(string originalPassword, string newPassword)
        {
            bool retVal = false;
            if (!string.IsNullOrEmpty(password) && string.IsNullOrEmpty(originalPassword))
            {
                throw new ApplicationException("Original password must be supplied if password already set.");
            }
            if (string.IsNullOrEmpty(password) || (AuthenticatePassword(originalPassword) && !AuthenticatePassword(newPassword)))
            {
                lastChangedPassword = DateTime.UtcNow;
                CreateSalt();
                password = ComputeSaltedHash(newPassword);
                retVal = true;
            }
            return retVal;
        }

        public bool AuthenticatePassword(string password)
        {
            string tmp = ComputeSaltedHash(password);
            return tmp == this.password;
        }

        private string ComputeSaltedHash(string password)
        {
            // Create Byte array of password string
            ASCIIEncoding encoder = new ASCIIEncoding();
            Byte[] secretBytes = encoder.GetBytes(password);

            // Create a new salt
            Byte[] saltBytes = new Byte[4];
            saltBytes[0] = (byte)(salt >> 24);
            saltBytes[1] = (byte)(salt >> 16);
            saltBytes[2] = (byte)(salt >> 8);
            saltBytes[3] = (byte)(salt);

            // append the two arrays
            Byte[] toHash = new Byte[secretBytes.Length + saltBytes.Length];
            Array.Copy(secretBytes, 0, toHash, 0, secretBytes.Length);
            Array.Copy(saltBytes, 0, toHash, secretBytes.Length, saltBytes.Length);

            SHA1 sha1 = SHA1.Create();
            Byte[] computedHash = sha1.ComputeHash(toHash);

            return encoder.GetString(computedHash);
        }
        #endregion


        public bool ValidatePasswordAnswer(string answer)
        {
            if (this.passwordAnswer == null && answer == null)
            {
                return true;
            }
            else if (this.passwordAnswer == null || answer == null)
            {
                return false;
            }
            else
            {
                return this.passwordAnswer.ToLower() == answer.ToLower();
            }
        }

        public string ResetPassword()
        {
            CreateSalt();
            string newPassword = GenerateNewPassword();
            password = ComputeSaltedHash(password);
            return newPassword;
        }

        private string GenerateNewPassword()
        {
            Random seed = new Random();
            Random rnd = new Random(seed.Next());

            char[] alphaChars = new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
            char[] numberic = new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
            char[] nonAlphNumeric = new char[] { '#', '!', '%', '*' };

            int passwordLength = 10;
            int noNonAlphNumeric = 1;
            int noNumeric = 2;

            int[] passwordFormat = new int[passwordLength];

            int noNumericSet = 0;
            while (noNumericSet < noNumeric)
            {
                int pos = rnd.Next(passwordLength);
                if (passwordFormat[pos] == 0)
                {
                    passwordFormat[pos] = 1;
                    noNumericSet++;
                }
            }
            
            int noNonAlphNumericSet = 0;
            while (noNonAlphNumericSet < noNonAlphNumeric)
            {
                int pos = rnd.Next(passwordLength);
                if (passwordFormat[pos] == 0)
                {
                    passwordFormat[pos] = 2;
                    noNonAlphNumericSet++;
                }
            }

            StringBuilder tmp = new StringBuilder();
            for (int i = 0; i < passwordFormat.Length; i++)
            {
                switch (passwordFormat[i])
                {
                    case 0:
                        tmp.Append(alphaChars[rnd.Next(alphaChars.Length)]);
                        break;
                    case 1:
                        tmp.Append(numberic[rnd.Next(numberic.Length)]);
                        break;
                    case 2:
                        tmp.Append(nonAlphNumeric[rnd.Next(nonAlphNumeric.Length)]);
                        break;
                }
            }
            return tmp.ToString();
        }

        public void Unlock()
        {
            locked = false;
        }
    }
}
